You make a typo in a program, say you forget a closing bracket. Within a second, before anything has run, an editor underlines the line and says expected ')'. Something has read your text, understood its structure well enough to know a bracket is missing, and told you where. Nobody sat down and wrote a rule for your particular typo.
Later you write 7 / 2 and get 3.5 in one language and 3 in another. Same symbols, different answers. And you hear, in the same week, that one language "has no garbage collector", another "checks everything at compile time", and a third "is just interpreted". These phrases are the vocabulary of a course that most programmers pick up in pieces. This piece puts the pieces together.
Who takes it: Computer Science majors, usually in year 3; also anyone building tools, frameworks or developer platforms.
What it's for: how a programming language is defined, how its programs are translated to run on a machine, and what design choices (types, memory, style) trade against each other.
Comes after: Data Structures (34/23) and Theory of Computation (34/29).
Leads to: no later subject in this series lists it as a prerequisite, but it underlies Software Engineering (34/31), tells you what Computer Architecture (34/25) runs, and shows up in Computer Security (34/33), where many exploits are memory bugs.
Honest line: you won't write a production compiler after this; you will see what a compiler does to your code and why languages differ the way they do. For what the machine does with the instructions at the end, Processors: How a CPU Thinks (29/11) is the easier first read.
Every programming language has two layers. Syntax is which strings of characters count as a program: where the brackets go, which words are reserved. Semantics is what a valid program means: what it does when it runs. Syntax is the spelling and grammar; semantics is the sense.
The two come apart more than newcomers expect. The same syntax can have different semantics in different languages, and most arguments about "which language is better" are really about semantics.
Languages also differ in how they are defined. Some have a long formal specification; some are defined by their main implementation; many are defined by a standard that a committee maintains. The notation used to write down a language's grammar, with rules like "an expression is a term, optionally followed by plus or minus and another term", is the first tool the rest of this piece uses.
A processor understands only its own machine instructions. A program in a human-friendly language must be translated. There are two basic ways:
Real systems blur the line. Python compiles your source into bytecode for a virtual machine, and runs that (its documentation includes a disassembler for the bytecode). Java compiles to bytecode for the Java Virtual Machine, and the JVM can compile hot parts to machine code while the program runs, a technique called just-in-time (JIT) compilation; modern JavaScript engines do the same. So "is language X compiled or interpreted?" is usually a question about one implementation, not about the language.
The idea of a compiler is old. Grace Hopper's A-0 system (1951–52) is often called an early compiler-related tool, and IBM's FORTRAN, led by John Backus, shipped in 1957 with a compiler good enough to convince working programmers that they no longer needed to write machine code by hand.
A classical compiler is a pipeline, and each stage has one job.
12+x becomes the tokens 12, +, x.2+3*4, the multiplication sits below the addition, so it happens first.2*3 now rather than every time the program runs (constant folding).The tree is the most important idea. Once the program is a tree, an interpreter can walk it to compute the answer, and a compiler can walk it to print instructions. Same tree, two different walks.
x, + - * / and brackets. Every stage is real code running in this page. For example, 2*3+x*(4+5) becomes the tree (+ (* 2 3) (* x (+ 4 5))); with x = 2 it evaluates to 24; constant folding rewrites it as (+ 6 (* x 9)), shortening the generated code from 9 instructions to 5.Notice what the type-checking and optimisation stages buy. Without a tree there is nowhere to ask "is this part constant?" or "does this add a number to text?". Real compilers do hundreds of such rewrites and work over several intermediate forms, but each is the same move: walk the structure, change it, keep the meaning.
A type is a label on a value that says what you may do with it: you can add numbers, you cannot add a number to a calendar. Languages split by when they check.
The debate is real, and both camps have a case. Static advocates point out that a whole class of mistakes disappears before shipping, that types document the code and make large refactors safer, and that tools can offer better autocompletion. Dynamic advocates point to quick experiments, shorter code and flexibility, and say good tests catch most of what types would. Evidence is mixed: one widely cited study (Gao, Bird and Barr, 2017) took real public JavaScript bugs and found that adding TypeScript or Flow annotations would have detected about 15% of them. The authors present that as a lower bound, because these were bugs that had survived testing and review; critics note it still means most bugs are not type errors. Both camps now converge somewhat: Python has optional type hints and JavaScript has TypeScript, an approach called gradual typing.
The sharp way to see the difference is in how a bug hides. A static check looks at every line. A dynamic check, or a test, sees only the lines that run.
This is the whole case for static types in one line: they check the rare path too. And the whole case against them: they only check the kinds of mistakes the type system can express, and they add ceremony. Neither replaces tests for logic errors.
Programs make objects in memory and must give the space back. Languages have three broad answers.
malloc and free). It is fast and predictable, and the mistakes are costly: forget and you leak; free twice or use after freeing and you get crashes and security holes.free. Rust 1.0 was announced in May 2015.Rust's trick is that assignment moves a value, and the old name stops working:
let s1 = String::from("hello");
let s2 = s1; // ownership moves to s2
println!("{}", s1); // compile error: borrow of moved value `s1`
That error appears at compile time, not at 3 a.m. in production, and it is the price of Rust's guarantees: you must learn to think about who owns what.
Garbage collectors come in flavours too. The simplest idea, reference counting, frees an object when nothing points at it. But two objects that point at each other keep each other alive forever. Tracing collectors instead start from the live variables (the roots), mark everything reachable, and sweep away the rest.
list and cache, point into a small heap. Drop one, then see what each strategy reclaims. E and F point at each other. Reference counting frees D when cache is dropped but leaks the E-F cycle; a tracing collector frees D, E and F.A paradigm is a style of organising a program. Most languages support more than one.
None of them wins everywhere. Many large systems mix all three, and a graduate learns to pick the style that fits the problem and to recognise it in whatever language they meet.
7 / 2 depends on the language.A programming language is syntax plus meaning, and a translator turns it into something a machine can run by lexing it, parsing it into a tree, checking it, improving it and generating code. The big design choices, when types are checked, who frees memory, how code is organised, are trade-offs with real costs on each side.
Try this: next time your editor or a build tool shows an error, name the stage that found it: a bad character (lexing), a missing bracket (parsing), a wrong type (checking). Then see how often you can guess before you read the message.